//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.google.gson.internal.bind;

import com.google.gson.FieldNamingStrategy;
import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.JsonParseException;
import com.google.gson.ReflectionAccessFilter;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.annotations.SerializedName;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.Excluder;
import com.google.gson.internal.ObjectConstructor;
import com.google.gson.internal.Primitives;
import com.google.gson.internal.ReflectionAccessFilterHelper;
import com.google.gson.internal.bind.JsonAdapterAnnotationTypeAdapterFactory;
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
import com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper;
import com.google.gson.internal.reflect.ReflectionHelper;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Type adapter that reflects over the fields and methods of a class.
 * gson版本2.10.1
 */
public final class MyReflectiveTypeAdapterFactory implements TypeAdapterFactory {
    private final ConstructorConstructor constructorConstructor;
    private final FieldNamingStrategy fieldNamingPolicy;
    private final Excluder excluder;
    private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory;
    private final List<ReflectionAccessFilter> reflectionFilters;

    public MyReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
                                          FieldNamingStrategy fieldNamingPolicy, Excluder excluder,
                                          JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory,
                                          List<ReflectionAccessFilter> reflectionFilters) {
        this.constructorConstructor = constructorConstructor;
        this.fieldNamingPolicy = fieldNamingPolicy;
        this.excluder = excluder;
        this.jsonAdapterFactory = jsonAdapterFactory;
        this.reflectionFilters = reflectionFilters;
    }

    private boolean includeField(Field f, boolean serialize) {
        return !excluder.excludeClass(f.getType(), serialize) && !excluder.excludeField(f, serialize);
    }

    /**
     * first element holds the default name
     */
    private List<String> getFieldNames(Field f) {
        SerializedName annotation = f.getAnnotation(SerializedName.class);
        if (annotation == null) {
            String name = fieldNamingPolicy.translateName(f);
            return Collections.singletonList(name);
        }

        String serializedName = annotation.value();
        String[] alternates = annotation.alternate();
        if (alternates.length == 0) {
            return Collections.singletonList(serializedName);
        }

        List<String> fieldNames = new ArrayList<>(alternates.length + 1);
        fieldNames.add(serializedName);
        Collections.addAll(fieldNames, alternates);
        return fieldNames;
    }

    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        TypeAdapter<T> adapter = gson.getDelegateAdapter(this, type);
        Class<? super T> raw = type.getRawType();
        ReflectionAccessFilter.FilterResult filterResult =
                ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw);
        boolean blockInaccessible = filterResult == ReflectionAccessFilter.FilterResult.BLOCK_INACCESSIBLE;
        switch (adapter.getClass().getName()) {
            case "com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$RecordAdapter":
                return (TypeAdapter<T>) new RecordAdapter<>(raw, getBoundFields(gson, type, raw, blockInaccessible, true), blockInaccessible);
            case "com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$FieldReflectionAdapter":
                return new FieldReflectionAdapter<>(constructorConstructor.get(type), getBoundFields(gson, type, raw, blockInaccessible, false));
            default:
                return null;
        }
    }

    private static <M extends AccessibleObject & Member> void checkAccessible(Object object, M member) {
        if (!ReflectionAccessFilterHelper.canAccess(member, Modifier.isStatic(member.getModifiers()) ? null : object)) {
            String memberDescription = ReflectionHelper.getAccessibleObjectDescription(member, true);
            throw new JsonIOException(memberDescription + " is not accessible and ReflectionAccessFilter does not"
                    + " permit making it accessible. Register a TypeAdapter for the declaring type, adjust the"
                    + " access filter or increase the visibility of the element and its declaring type.");
        }
    }

    private ReflectiveTypeAdapterFactory.BoundField createBoundField(
            final Gson context, final Field field, final Method accessor, final String name,
            final TypeToken<?> fieldType, boolean serialize, boolean deserialize,
            final boolean blockInaccessible) {

        final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());

        int modifiers = field.getModifiers();
        final boolean isStaticFinalField = Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers);

        JsonAdapter annotation = field.getAnnotation(JsonAdapter.class);
        TypeAdapter<?> mapped = null;
        if (annotation != null) {
            // This is not safe; requires that user has specified correct adapter class for @JsonAdapter
            mapped = jsonAdapterFactory.getTypeAdapter(
                    constructorConstructor, context, fieldType, annotation);
        }
        final boolean jsonAdapterPresent = mapped != null;
        if (mapped == null) mapped = context.getAdapter(fieldType);

        @SuppressWarnings("unchecked") final TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) mapped;
        return new ReflectiveTypeAdapterFactory.BoundField(name, field, serialize, deserialize) {
            @Override
            void write(JsonWriter writer, Object source)
                    throws IOException, IllegalAccessException {
                if (!serialized) return;
                if (blockInaccessible) {
                    if (accessor == null) {
                        checkAccessible(source, field);
                    } else {
                        // Note: This check might actually be redundant because access check for canonical
                        // constructor should have failed already
                        checkAccessible(source, accessor);
                    }
                }

                Object fieldValue;
                if (accessor != null) {
                    try {
                        fieldValue = accessor.invoke(source);
                    } catch (InvocationTargetException e) {
                        String accessorDescription = ReflectionHelper.getAccessibleObjectDescription(accessor, false);
                        throw new JsonIOException("Accessor " + accessorDescription + " threw exception", e.getCause());
                    }
                } else {
                    fieldValue = field.get(source);
                }
                if (fieldValue == source) {
                    // avoid direct recursion
                    return;
                }
                writer.name(name);
                TypeAdapter<Object> t = jsonAdapterPresent ? typeAdapter
                        : new TypeAdapterRuntimeTypeWrapper<>(context, typeAdapter, fieldType.getType());
                t.write(writer, fieldValue);
            }

            @Override
            void readIntoArray(JsonReader reader, int index, Object[] target) throws IOException, JsonParseException {
                Object fieldValue = typeAdapter.read(reader);
                if (fieldValue == null && isPrimitive) {
                    throw new JsonParseException("null is not allowed as value for record component '" + fieldName + "'"
                            + " of primitive type; at path " + reader.getPath());
                }
                target[index] = fieldValue;
            }

            @Override
            void readIntoField(JsonReader reader, Object target)
                    throws IOException, IllegalAccessException {
                Object fieldValue = typeAdapter.read(reader);
                if (fieldValue != null) {
                    if (blockInaccessible) {
                        checkAccessible(target, field);
                    } else if (isStaticFinalField) {
                        // Reflection does not permit setting value of `static final` field, even after calling `setAccessible`
                        // Handle this here to avoid causing IllegalAccessException when calling `Field.set`
                        String fieldDescription = ReflectionHelper.getAccessibleObjectDescription(field, false);
                        throw new JsonIOException("Cannot set value of 'static final' " + fieldDescription);
                    }
                    field.set(target, fieldValue);
                }
            }
        };
    }

    private Map<String, ReflectiveTypeAdapterFactory.BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw,
                                                                                boolean blockInaccessible, boolean isRecord) {
        Map<String, ReflectiveTypeAdapterFactory.BoundField> result = new LinkedHashMap<>();
        if (raw.isInterface()) {
            return result;
        }

        Class<?> originalRaw = raw;
        while (raw != Object.class) {
            Field[] fields = raw.getDeclaredFields();

            // For inherited fields, check if access to their declaring class is allowed
            if (raw != originalRaw && fields.length > 0) {
                ReflectionAccessFilter.FilterResult filterResult = ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw);
                if (filterResult == ReflectionAccessFilter.FilterResult.BLOCK_ALL) {
                    throw new JsonIOException("ReflectionAccessFilter does not permit using reflection for " + raw
                            + " (supertype of " + originalRaw + "). Register a TypeAdapter for this type"
                            + " or adjust the access filter.");
                }
                blockInaccessible = filterResult == ReflectionAccessFilter.FilterResult.BLOCK_INACCESSIBLE;
            }

            for (Field field : fields) {
                boolean serialize = includeField(field, true);
                boolean deserialize = includeField(field, false);
                if (!serialize && !deserialize) {
                    continue;
                }
                // The accessor method is only used for records. If the type is a record, we will read out values
                // via its accessor method instead of via reflection. This way we will bypass the accessible restrictions
                Method accessor = null;
                if (isRecord) {
                    // If there is a static field on a record, there will not be an accessor. Instead we will use the default
                    // field serialization logic, but for deserialization the field is excluded for simplicity. Note that Gson
                    // ignores static fields by default, but GsonBuilder.excludeFieldsWithModifiers can overwrite this.
                    if (Modifier.isStatic(field.getModifiers())) {
                        deserialize = false;
                    } else {
                        accessor = ReflectionHelper.getAccessor(raw, field);
                        // If blockInaccessible, skip and perform access check later
                        if (!blockInaccessible) {
                            ReflectionHelper.makeAccessible(accessor);
                        }

                        // @SerializedName can be placed on accessor method, but it is not supported there
                        // If field and method have annotation it is not easily possible to determine if accessor method
                        // is implicit and has inherited annotation, or if it is explicitly declared with custom annotation
                        if (accessor.getAnnotation(SerializedName.class) != null
                                && field.getAnnotation(SerializedName.class) == null) {
                            String methodDescription = ReflectionHelper.getAccessibleObjectDescription(accessor, false);
                            throw new JsonIOException("@SerializedName on " + methodDescription + " is not supported");
                        }
                    }
                }

                // If blockInaccessible, skip and perform access check later
                // For Records if the accessor method is used the field does not have to be made accessible
                if (!blockInaccessible && accessor == null) {
                    ReflectionHelper.makeAccessible(field);
                }
                Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
                List<String> fieldNames = getFieldNames(field);
                ReflectiveTypeAdapterFactory.BoundField previous = null;
                for (int i = 0, size = fieldNames.size(); i < size; ++i) {
                    String name = fieldNames.get(i);
                    if (i != 0) serialize = false; // only serialize the default name
                    ReflectiveTypeAdapterFactory.BoundField boundField = createBoundField(context, field, accessor, name,
                            TypeToken.get(fieldType), serialize, deserialize, blockInaccessible);
                    ReflectiveTypeAdapterFactory.BoundField replaced = result.put(name, boundField);
                    if (previous == null) previous = replaced;
                }
                if (previous != null) {
                    throw new IllegalArgumentException("Class " + originalRaw.getName()
                            + " declares multiple JSON fields named '" + previous.name + "'; conflict is caused"
                            + " by fields " + ReflectionHelper.fieldToString(previous.field) + " and " + ReflectionHelper.fieldToString(field));
                }
            }
            type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
            raw = type.getRawType();
        }
        return result;
    }

    private static final class RecordAdapter<T> extends ReflectiveTypeAdapterFactory.Adapter<T, Object[]> {
        static final Map<Class<?>, Object> PRIMITIVE_DEFAULTS = primitiveDefaults();

        // The canonical constructor of the record
        private final Constructor<T> constructor;
        // Array of arguments to the constructor, initialized with default values for primitives
        private final Object[] constructorArgsDefaults;
        // Map from component names to index into the constructors arguments.
        private final Map<String, Integer> componentIndices = new HashMap<>();

        RecordAdapter(Class<T> raw, Map<String, ReflectiveTypeAdapterFactory.BoundField> boundFields, boolean blockInaccessible) {
            super(boundFields);
            constructor = ReflectionHelper.getCanonicalRecordConstructor(raw);

            if (blockInaccessible) {
                checkAccessible(null, constructor);
            } else {
                // Ensure the constructor is accessible
                ReflectionHelper.makeAccessible(constructor);
            }

            String[] componentNames = ReflectionHelper.getRecordComponentNames(raw);
            for (int i = 0; i < componentNames.length; i++) {
                componentIndices.put(componentNames[i], i);
            }
            Class<?>[] parameterTypes = constructor.getParameterTypes();

            // We need to ensure that we are passing non-null values to primitive fields in the constructor. To do this,
            // we create an Object[] where all primitives are initialized to non-null values.
            constructorArgsDefaults = new Object[parameterTypes.length];
            for (int i = 0; i < parameterTypes.length; i++) {
                // This will correctly be null for non-primitive types:
                constructorArgsDefaults[i] = PRIMITIVE_DEFAULTS.get(parameterTypes[i]);
            }
        }

        private static Map<Class<?>, Object> primitiveDefaults() {
            Map<Class<?>, Object> zeroes = new HashMap<>();
            zeroes.put(byte.class, (byte) 0);
            zeroes.put(short.class, (short) 0);
            zeroes.put(int.class, 0);
            zeroes.put(long.class, 0L);
            zeroes.put(float.class, 0F);
            zeroes.put(double.class, 0D);
            zeroes.put(char.class, '\0');
            zeroes.put(boolean.class, false);
            return zeroes;
        }

        @Override
        Object[] createAccumulator() {
            return constructorArgsDefaults.clone();
        }

        @Override
        void readField(Object[] accumulator, JsonReader in, ReflectiveTypeAdapterFactory.BoundField field) throws IOException {
            // Obtain the component index from the name of the field backing it
            Integer componentIndex = componentIndices.get(field.fieldName);
            if (componentIndex == null) {
                throw new IllegalStateException(
                        "Could not find the index in the constructor '" + ReflectionHelper.constructorToString(constructor) + "'"
                                + " for field with name '" + field.fieldName + "',"
                                + " unable to determine which argument in the constructor the field corresponds"
                                + " to. This is unexpected behavior, as we expect the RecordComponents to have the"
                                + " same names as the fields in the Java class, and that the order of the"
                                + " RecordComponents is the same as the order of the canonical constructor parameters.");
            }
            field.readIntoArray(in, componentIndex, accumulator);
        }

        @Override
        T finalize(Object[] accumulator) {
            try {
                return constructor.newInstance(accumulator);
            } catch (IllegalAccessException e) {
                throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);
            }
            // Note: InstantiationException should be impossible because record class is not abstract;
            //  IllegalArgumentException should not be possible unless a bad adapter returns objects of the wrong type
            catch (InstantiationException | IllegalArgumentException e) {
                throw new RuntimeException(
                        "Failed to invoke constructor '" + ReflectionHelper.constructorToString(constructor) + "'"
                                + " with args " + Arrays.toString(accumulator), e);
            } catch (InvocationTargetException e) {
                // TO--DO: JsonParseException ?
                throw new RuntimeException(
                        "Failed to invoke constructor '" + ReflectionHelper.constructorToString(constructor) + "'"
                                + " with args " + Arrays.toString(accumulator), e.getCause());
            }
        }

        @Override
        public T read(JsonReader in) throws IOException {
            if (in.peek() == JsonToken.STRING) {
                String st = in.nextString();
                if (st == null || st.isEmpty()) {
                    return null;
                }
                return super.read(new JsonReader(new StringReader(st)));
            } else {
                return super.read(in);
            }
        }
    }

    private static final class FieldReflectionAdapter<T> extends ReflectiveTypeAdapterFactory.Adapter<T, T> {
        private final ObjectConstructor<T> constructor;

        FieldReflectionAdapter(ObjectConstructor<T> constructor, Map<String, ReflectiveTypeAdapterFactory.BoundField> boundFields) {
            super(boundFields);
            this.constructor = constructor;
        }

        @Override
        T createAccumulator() {
            return constructor.construct();
        }

        @Override
        void readField(T accumulator, JsonReader in, ReflectiveTypeAdapterFactory.BoundField field)
                throws IllegalAccessException, IOException {
            field.readIntoField(in, accumulator);
        }

        @Override
        T finalize(T accumulator) {
            return accumulator;
        }

        @Override
        public T read(JsonReader in) throws IOException {
            if (in.peek() == JsonToken.STRING) {
                String st = in.nextString();
                if (st == null || st.isEmpty()) {
                    return null;
                }
                return super.read(new JsonReader(new StringReader(st)));
            } else {
                return super.read(in);
            }
        }
    }
}
